﻿using System;
using System.Text;
using TallComponents.PDF.Navigation;
using System.Web;
using System.Globalization;

namespace TallComponents.Web.Pdf.Annotations
{
   /// <summary>
   /// Generates JavaScript to be executed when a link is clicked.
   /// </summary>
   internal class WebActionVisitor : TallComponents.PDF.Actions.ActionVisitor
   {
      public WebActionVisitor( INamedDestinationResolver namedDestinationResolver )
      {
         if ( null == namedDestinationResolver )
            throw new ArgumentNullException( "namedDestinationResolver" );

         _namedDestinationResolver = namedDestinationResolver;
      }

      /// <summary>
      /// Gets the generated javascript.
      /// </summary>
      /// <value>The generated javascript.</value>
      public string Javascript
      {
         get
         {
            return sb.ToString();
         }
      }

      /// <summary>
      /// Visit a NamedAction.
      /// </summary>
      /// <param name="action">The vistor must handle a NamedAction.</param>
      /// <param name="argument">The additional argument passed to the Accept method (can be <see langword="null"/>).</param>
      /// <returns></returns>
      /// <remarks>
      /// Not implementing this functionality calls the action overload.
      /// </remarks>
      public override object Visit( TallComponents.PDF.Actions.NamedAction action, object argument )
      {
         switch( action.Name.ToLowerInvariant() )
         {
            case "nextpage":
               sb.AppendFormat( "this.goTo('next',null,null);" );
               break;
            case "prevpage":
               sb.AppendFormat( "this.goTo('previous',null,null);" );
               break;
            case "firstpage":
               sb.AppendFormat( "this.goTo('first',null,null);" );
               break;
            case "lastpage":
               sb.AppendFormat( "this.goTo('last',null,null);" );
               break;
         }

         return null;
      }

      /// <summary>
      /// Visit a GoToAction.
      /// </summary>
      /// <param name="action">The vistor must handle a GoToAction.</param>
      /// <param name="argument">The additional argument passed to the Accept method (can be <see langword="null"/>).</param>
      /// <returns></returns>
      /// <remarks>
      /// Not implementing this functionality calls the action overload.
      /// </remarks>
      public override object Visit( TallComponents.PDF.Actions.GoToAction action, object argument )
      {
         VisitDestination( action.Destination );
         return null;
      }

      /// <summary>
      /// Visit an UriAction.
      /// </summary>
      /// <param name="action">The vistor must handle an UriAction.</param>
      /// <param name="argument">The additional argument passed to the Accept method (can be <see langword="null"/>).</param>
      /// <returns></returns>
      /// <remarks>
      /// Not implementing this functionality calls the action overload.
      /// </remarks>
      public override object Visit( TallComponents.PDF.Actions.UriAction action, object argument )
      {
         if( !string.IsNullOrEmpty( action.Uri ) )
         {
            string target = "_blank";
            if ( action.Uri.StartsWith( "mailto:", StringComparison.OrdinalIgnoreCase ) )
            {
               target = "_self";
            }
            
            sb.AppendFormat( "window.open('{0}','{1}');", HttpUtility.HtmlAttributeEncode( action.Uri ), target );
         }
         return null;
      }

      private void VisitDestination( Destination dest )
      {
         var internalDest = dest as InternalDestination;

         if ( null != internalDest )
         {
            BuildGoToScript( sb, internalDest );
         }
         else
         {
            var namedDest = dest as NamedDestination;
            if ( null != namedDest )
            {
               Destination actualDest;
               if ( _namedDestinationResolver.TryResolve( namedDest.Name, out actualDest ) )
               {
                  VisitDestination( actualDest );
               }
            }
         }

         // Unsupported destination types :
         // - RemoteDest
      }

      private static void BuildSetPageScript( StringBuilder sb, int pageIndex )
      {
         sb.AppendFormat( "this.set_pageIndex({0});", pageIndex );
      }

      private static void BuildGoToScript( StringBuilder sb, InternalDestination internalDest )
      {
         string zoom = "";
         string position = "null";

         if ( internalDest.Page != null )
         {
            switch ( internalDest.PageDisplay )
            {
               case PageDisplay.FitRectangle:
                  //  Fit the specified rectangle. 
                  //  The rectangle is specified by the Left, Right, Top and Bottom properties of Bookmark.
                  
                  // try to calculate zoomfactor
                  zoom = "NaN";
                  var width = Limit( internalDest.Right, internalDest.Page.Width ) -
                              Limit( internalDest.Left, internalDest.Page.Width );
                  var height = Limit( internalDest.Top, internalDest.Page.Height ) -
                              Limit( internalDest.Bottom, internalDest.Page.Height );
                  position = "{" + string.Format( CultureInfo.InvariantCulture, 
                     "'x':{0:0.##}, 'y':{1:0.##},'width':{2:0.##}, 'height':{3:0.##}", 
                     Limit( internalDest.Left, internalDest.Page.Width ), 
                     internalDest.Page.Height - Limit( internalDest.Top, internalDest.Page.Height ), 
                     width, height ) + "}";
                  break;
               case PageDisplay.PositionZoom:
                  // Zoom factor has found to be NaN at times; handle by jumping to page and ignoring other settings.
                  if ( double.IsNaN( internalDest.ZoomFactor ) )
                  {
                     BuildSetPageScript( sb, internalDest.Page.Index );
                     return;
                  }
                  // The page is displayed with specified zoom and specified position. 
                  // The top and left positions and the zoom are specified by the Top, Left and Zoom properties of Bookmark.  
                  zoom = internalDest.ZoomFactor.ToString( "0.##", CultureInfo.InvariantCulture );
                  position = FormatPosition( internalDest );
                  break;
               case PageDisplay.FitBoundingBox:
                  //  Fit the bounding box of the page contents both horizontally and vertically.  
                  //  Not supported yet; default to fit to page
                  zoom = "'page'";
                  break;
               case PageDisplay.FitBoundingBoxVertically:
               case PageDisplay.FitVertically:
                  //  Fit the page vertically. The left position is specified by the Left property of Bookmark.  
                  //  Not supported yet; default to fit to page
                  zoom = "'page'";
                  break;

               case PageDisplay.FitEntire:
                  // Fit the entire page both horizontally and vertically.  
                  zoom = "'page'";
                  break;
               case PageDisplay.FitBoundingBoxHorizontally:
               case PageDisplay.FitHorizontally:
                  //  Fit the page horizontally. The top position is specified by the Top property of Bookmark.
                  if ( !double.IsNaN( internalDest.Top ) )
                  {
                     position = FormatPosition( internalDest );
                  }
                  zoom = "'width'";
                  break;
               default:
                  zoom = "null"; // null means 'use current'
                  break;
            }

            sb.AppendFormat( "this.goTo({0},{1},{2});", internalDest.Page.Index, zoom, position );
         }
      }

      private static string FormatPosition( InternalDestination internalDest )
      {

         return string.Format(CultureInfo.InvariantCulture, " new TallComponents.Vector2({0:0.##}, {1:0.##})",
                                                Double.IsNaN( internalDest.Left ) ? 0 : internalDest.Left,
                                                Double.IsNaN(internalDest.Top) ? 0 : internalDest.Page.Height - internalDest.Top);
      }

      private static double Limit( double value, double maxValue )
      {
         if( value > 0 )
         {
            return value <= maxValue ? value : maxValue;
         }
         return 0;
      }

      INamedDestinationResolver _namedDestinationResolver;
      StringBuilder sb = new StringBuilder();
   }


}
